package com.theorydance.esoperator.utils;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

/**
 * 针对使用apache的httpclient的http获取请求，经过反复测试，发现其不具备优势，与自己通过原始的URLConnection
 * 对比，发现apache的httpclient创建时间长，而且在复用的时候，每次请求都比原始的URLConnection直接获取数据耗时多几倍；
 * 测试见：TestHttpPerformanceByMulti2.java
 */
public class HttpPoolUtil {
	
	// 全局使用的httpClient
	private static CloseableHttpClient httpClient = null;
	private final static int MAX_THREAD = 30;
	private final static int MAX_THREAD_PER_ROUTE = 10;
	private final static String[] ROUTE_ARRAY = new String[]{"www.baidu.com"};
	
	static {
		initHttpClient();
	}
	
	public static CloseableHttpClient getHttpClient() {
		// 采用双重验证方式，避免重复创建httpClient对象
		if (httpClient == null) {
			synchronized (HttpPoolUtil.class) {
				if (httpClient == null) {
					initHttpClient();
				}
			}
		}
		return httpClient;
	}

	private static void initHttpClient() {
		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
		// 总连接池数量
		connectionManager.setMaxTotal(MAX_THREAD);
		// 可为每个域名设置单独的连接池数量
		for(String route : ROUTE_ARRAY) {
			connectionManager.setMaxPerRoute(new HttpRoute(new HttpHost(route)), MAX_THREAD_PER_ROUTE);
		}
		
		/* setConnectTimeout表示设置建立连接的超时时间
		 * setConnectionRequestTimeout表示从连接池中拿连接的等待超时时间
		 * setSocketTimeout表示发出请求后等待对端应答的超时时间
		 */ 
		RequestConfig requestConfig = RequestConfig.custom()
				.setConnectTimeout(1000)
				.setConnectionRequestTimeout(2000)
				.setSocketTimeout(3000)
				.build();
		// 重试处理器，StandardHttpRequestRetryHandler这个是官方提供的
		HttpRequestRetryHandler retryHandler = new StandardHttpRequestRetryHandler();
		
		httpClient = HttpClients.custom()
				.setConnectionManager(connectionManager)
				.setDefaultRequestConfig(requestConfig)
				.setRetryHandler(retryHandler)
				.build();
		// 服务端假设关闭了连接，对客户端是不透明的，HttpClient为了缓解这一问题，在某个连接使用前会检测这个连接是否过时，如果过时则连接失效，但是这种做法会为每个请求
		// 增加一个额外开销，因此有一个定时任务专门回收长时间不活动而被判定为失效的连接，可以某种程度上解决这个问题
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				// 关闭失效连接并从连接池中移除
				connectionManager.closeExpiredConnections();
				// 关闭30秒内不活动的连接并从连接池中移除，空闲时间从交还给连接管理器的开始
				connectionManager.closeIdleConnections(20, TimeUnit.SECONDS);
			}
		}, 0, 1000*5);
	}
	
	public static String get(String requestUrl) {
		String responseBody = null;
		// httpClient是线程安全的，因此HttpClient正常使用应当做成全局变量，HttpClient内部构建的时候会new一个连接池出来 ，
		HttpGet httpGet = new HttpGet(requestUrl);
		try {
			CloseableHttpResponse response = httpClient.execute(httpGet);
			if (response != null) {
				responseBody = EntityUtils.toString(response.getEntity(), Tools.UTF8);
				response.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {}
		return responseBody;
	}
	
	public static String postJSON(String requestUrl, String body) {
    	String responseBody = null;
		try {
			HttpPost httpPost = new HttpPost(requestUrl);
			httpPost.setHeader("Content-Type", "application/json");
			httpPost.setEntity(new StringEntity(body, Tools.UTF8));
			CloseableHttpResponse response = httpClient.execute(httpPost);
			responseBody = EntityUtils.toString(response.getEntity(), Tools.UTF8);
		} catch (Exception e) {
			e.printStackTrace();
		}
    	return responseBody;
    }

}
